#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <boost/filesystem.hpp>
#include "Util.hpp"

const std::string src_path = "data/input/"; // html资源存放地址
const std::string output_path = "data/output/raw_html.txt"; // 输出解析后文本的地址

// 用于存放解析后资源的类
typedef class DocInfo
{
public:
    std::string title;
    std::string content;
    std::string url;
}DocInfo_t;


bool EnumFile(const std::string& src_path, std::vector<std::string>* pfiles_list); // 输出src_path下所有html文件的文件名
bool ParseHtml(const std::vector<std::string>& files_list, std::vector<DocInfo_t>* presults); // 解析src_path下所有html文件，将其保存到vector<DocInfo_t>中
bool SaveHtml(const std::string& output_path, const std::vector<DocInfo_t>& results); // 将上面解析好的资源输出到output_path的文件中

int main()
{
    std::vector<std::string> files_list;
    // 1.获取data/input目录下html文件名称
    if (!EnumFile(src_path, &files_list))
    {
        std::cerr << "enum file failed" << std::endl;
        return 1;
    }
    // 2.从上述html文件中解析出title，content以及url
    std::vector<DocInfo_t> results;
    if(!ParseHtml(files_list, &results))
    {
        std::cerr << "parse file failed" << std::endl;
        return 2;
    }

    // 3.将解析出的信息保存到data/output/raw_html.txt中
    if(!SaveHtml(output_path, results))
    {
        std::cerr << "save file failed" << std::endl;
        return 3;
    }
    return 0;
}

bool EnumFile(const std::string& src_path, std::vector<std::string>* pfiles_list)
{
    namespace fs = boost::filesystem;// 这里要使用boost::file_system中的函数，先重命名一下命名空间，方便后续使用
    fs::path root_path(src_path); // boost中的path类
    fs::recursive_directory_iterator end;// 空的迭代器

    // 判断src_path是否为有效地址
    if(!fs::exists(root_path))
    {
        std::cerr << "src_path did't exist!" << std::endl;
        return false; 
    }

    for(fs::recursive_directory_iterator iter(root_path); iter != end; ++iter)
    {
        // 判断是否为普通文件
        if(!fs::is_regular_file(*iter))
        {
            continue;
        }
        // 判断是否为后缀为.html的文件
        if(iter->path().extension() != ".html")
        {
            continue;
        }
        // 到这里的文件就是.html文件
        // std::cout << "debug: " << iter->path().string() << std::endl;
        pfiles_list->push_back(iter->path().string()); // 将名字插入数组
    }

    return true;
}

static bool ParseTitle(const std::string& text, std::string* ptitle)
{
    size_t start = text.find("<title>"); // 获取<title>位置
    size_t end = text.find("</title>"); // 获取</title>位置
    start += std::string("<title>").size(); // 将start移到标题开始的位置 

    // 判断起始位置和结束位置合法性
    if(start > end)
    {
        std::cerr << "parse title failed" << std::endl;
        return false;
    }
    *ptitle = text.substr(start, end - start);
    return true;
}

// 解析内容有一个小bug，由于我们是直接将全文内容都读到content中，所以将上面解析的标题也算到了content中
// 最后统计词频时，在content中会多算一个title中的词
static bool ParseContent(const std::string& text, std::string* pcontent)
{
    // 利用简易的状态机去标签
    enum status
    {
        LABEL,
        CONTENT
    };

    status flag = LABEL; // 每篇html开始一般都是标签
    // 遍历读取text
    for(char c : text)
    {
       switch(flag)
       {
        // 状态为LABEL时，直接跳过，直到读到'>'
        case LABEL:
            if(c == '>')
                flag = CONTENT;
            break;
        // 状态为CONTENT时，读取文本
        case CONTENT:
            if(c == '<')
                flag = LABEL;
            else 
            {
                // \n 在后面要作为文档的分隔符，所以不能将\n 读进去
                if(c == '\n')
                    c = ' ';
                *pcontent += c;
            }
            break;
        default:
            std::cerr << "parse content failed" << std::endl;
            return false;
            break;
       } 
    }
    return true;
}

static bool ParseUrl(const std::string& file_path, std::string* purl)
{
    // 目标boost文库地址：https://www.boost.org/doc/libs/1_80_0/doc/html/xxxxx
    // 文档在本地的地址：data/input/xxxxx
    // 相同的头地址：https://www.boost.org/doc/libs/1_80_0/doc/html
    // 处理方法：将[data/input](删去)/xxxxx --> /xxxxx  再将 头地址和/xxxxx拼接
    static std::string html_head = "https://www.boost.org/doc/libs/1_80_0/doc/html";
    std::string html_tail = file_path.substr(std::string("data/input").size());

    *purl = html_head + html_tail;
    return true;
}


bool ParseHtml(const std::vector<std::string>& files_list, std::vector<DocInfo_t>* presults)
{
    for(auto& file : files_list)
    {
        // 0. 读取文件
        std::string text;
        if(!ns_util::FileUtil::ReadFile(file, &text))
        {
            std::cerr << "read file failed" << std::endl;
            return false;
        }

        DocInfo_t doc;
        // 1. 解析title
        if(!ParseTitle(text, &doc.title))
        {
            std::cerr << "parse title failed" << std::endl;
            return false;
        }
        // 2. 解析content
        if(!ParseContent(text, &doc.content))
        {
            std::cerr << "parse content failed" << std::endl;
            return false;
        }
        // 3. 解析url
        if(!ParseUrl(file, &doc.url))
        {
            std::cerr << "parse url failed" << std::endl;
            return false;
        }

        // for debug
        // std::cout << text << std::endl;
        // std::cout << doc.title << std::endl;
        // std::cout << doc.content << std::endl;
        // std::cout << doc.url << std::endl;
        // break;
        presults->push_back(std::move(doc));
    }
    
    return true;
}

bool SaveHtml(const std::string& output_path, const std::vector<DocInfo_t>& results)
{
#define SEP '\3'
    // 目标形式：title\3content\3url\n title\3content\3url\n
    // \3为^C，用于分割 DocInfo 中的信息，\n 用于分割不同文档的信息
    std::ofstream ofs(output_path, std::ios::out | std::ios::binary);
    if(!ofs.is_open())
    {
        std::cerr << "open " << output_path << std::endl;
        return false;
    }

    for(const DocInfo_t& result: results)
    {
        std::string buf;
        buf += result.title;
        buf += SEP;
        buf += result.content;
        buf += SEP;
        buf += result.url;
        buf += '\n';

        ofs.write(buf.c_str(), buf.size());
    }
    ofs.close();// 关闭文件
    return true;
}

